package com.tree.leetcode.dp;

/**
 * @ClassName com.tree.leetcode.dp
 * Description: 最长回文子串
 * <p>
 * https://leetcode.cn/problems/longest-palindromic-substring/description/?envType=study-plan-v2&envId=top-100-liked
 * 给你一个字符串 s，找到 s 中最长的回文子串。
 * <p>
 * 如果字符串的反序与原始字符串相同，则该字符串称为回文字符串。
 * <p>
 * <p>
 * <p>
 * 示例 1：
 * <p>
 * 输入：s = "babad"
 * 输出："bab"
 * 解释："aba" 同样是符合题意的答案。
 * 示例 2：
 * <p>
 * 输入：s = "cbbd"
 * 输出："bb"
 * <p>
 * <p>
 * 提示：
 * <p>
 * 1 <= s.length <= 1000
 * s 仅由数字和英文字母组成
 * </p>
 * @Author tree
 * @Date 12/7/23 10:33 PM
 * @Version 1.0
 */
public class LongestPalindrome_5 {

    public static void main(String[] args) {
        String s = "abcbad";
//        System.out.println(getLongestPalindrome(s));
//        System.out.println(getLongestPalindrome2(s));
        System.out.println(getLongestPalindrome3(s));


    }

    /**
     * 方法一：暴力解法
     *
     * @param s
     * @return
     */
    public static String getLongestPalindrome(String s) {
        char[] chars = s.toCharArray();
        ////因为要返回子串， 所以只需要记录开始位置和最大的长度即可
        int begin = 0;
        int maxLen = 1;

        //计算有多少种子串的情况, 每种情况都需要判断是不是回文，
        for (int i = 0; i < s.length() - 1; i++) {
            for (int j = i + 1; j < s.length(); j++) {
                //如果当前组合最大长度大于maxLen, 且当前是回文
                if ((j - i + 1 > maxLen) && isPalindrome(chars, i, j)) {
                    begin = i;
                    maxLen = j - i + 1;
                }
            }
        }
        return s.substring(begin, begin + maxLen);
    }

    /**
     * 是否是回文， 利用回文左右对称的特性， 如果左右不相等， 那么一定不是回文， 否则 则是回文
     * 利用左右指针判断
     */
    private static boolean isPalindrome(char s[], int left, int right) {
        System.out.println(new String(s) + "_" + left + "_" + right);
        String printString = new String(s).substring(left, right + 1);

        while (left < right) {
            //如果左右不相等， 那么一定不是回文
            if (s[left] != s[right]) {
                return false;
            }
            //否则， 左边右移， 右边左移
            left++;
            right--;
        }
        System.out.println("回文:" + printString);
        return true;
    }

    /**
     * 方法2： 动态规划
     * 1. 确定dp数组（dp table）以及下标的含义
     * dp[i][j] 表示 子串s[i...j]是否为回文
     * 2. 确定递推公式 及边界条件
     * dp[i][j] = (s[i] == s[j] ) and  dp[i+1] [j-1]
     * 边界条件,
     * * i == j  , true
     * * s[i] == s[j]  0 < j -i < 3(如果不构成区间， (j-1) - (i+1) < 1 --> j-i < 3)
     * * (s[i] == s[j] ) and  dp[i+1] [j-1] , j -i >= 3
     * <p>
     * 3. dp数组如何初始化
     * dp[i][i] = true
     * 4. 确定遍历顺序
     * 从前到后
     * 5. 举例推导dp数组
     * <p>
     * 参考： 最长回文子串动态规划示意图.png
     * https://www.bilibili.com/video/BV1dN4y1g7p9/?spm_id_from=333.337.search-card.all.click&vd_source=7e2be45040616926464653326437b06f
     */
    public static String getLongestPalindrome2(String s) {

        //字符串转成字符
        char[] charArray = s.toCharArray();

        //如果长度为1
        int len = s.length();
        if (len == 1) {
            return s;
        }

        //设置最大回文的初始位置及最大长度， 方便最后打印
        int maxLength = 1;
        int begin = 0;

        //定义一个存储所有可能的dp二维数组，
        boolean[][] dp = new boolean[len][len];

        //单字符串本来就是回文
        for (int i = 0; i < len; i++) {
            dp[i][i] = true;
        }

        // 对二维数组来遍历， 二维数组对角线上面区域
        for (int j = 1; j < len; j++) {
            for (int i = 0; i < j; i++) {
                if (charArray[i] != charArray[j]) {
                    dp[i][j] = false;
                } else {
                    if (j - i < 3) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }
                // 只要 dp[i][j] == true 成立，就表示子串 s[i..j] 是回文，此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLength) {
                    System.out.println("i=" + i + ";j=" + j + "; 回文串为：" + s.substring(begin, begin + maxLength));
                    maxLength = j - i + 1;
                    begin = i;
                }
            }

        }
        System.out.println(("i=" + begin + ";j=" + (begin + maxLength - 1) + "; 回文串为：" + s.substring(begin, begin + maxLength)));
        return s.substring(begin, begin + maxLength);


    }

    /**
     * 中心扩散法
     *
     * @param s
     * @return
     */
    public static String getLongestPalindrome3(String s) {

        if (s == null || s.length() < 1) {
            return "";
        }
        int start = 0, end = 0;

        for (int i = 0; i < s.length(); i++) {
            int oddLen = expandAroundCenter(s, i, i);
            int evanLen = expandAroundCenter(s, i, i + 1);
            int max = Math.max(oddLen, evanLen);
            if (max > end - start) {
                start = i - (max - 1) / 2;
                end = i + max / 2;
                System.out.println("i = " + i + ";start=" + start + "; end = " + (end + 1) + "； 回文串为：" + s.substring(start, end + 1));
            }
        }
        return s.substring(start, end + 1);

    }

    private static int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        //跳出while循环时，恰好满足s.charAt(left) != s.charAt(right)
        //回文串的长度是right-left+1-2=right-left-1
        return right - left - 1;
    }
}

